package com.ofek.eventos.db;


import hw.P2PConnection;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.support.v4.content.LocalBroadcastManager;

import com.ofek.applicationbase.broadcast.Broadcaster;
import com.ofek.applicationbase.broadcast.Delegate;
import com.ofek.applicationbase.utils.Log;
import com.ofek.eventos.Constants;
import com.ofek.eventos.EventosApplication;
import com.ofek.eventos.db.EventsDatabase1.EventFilter;
import com.ofek.eventos.db.datatypes.EventData;
import com.ofek.eventos.db.datatypes.Field;
import com.ofek.eventos.db.datatypes.Request;
import com.ofek.eventos.db.datatypes.SearchDefiner;
import com.ofek.eventos.db.datatypes.Type;

public class EventosDatabaseService extends Service {
	private Looper looper;
	private Handler handler; 

	public static final String EVENT_REMOVE_REQUEST = EventosDatabaseService.class.getName()+".EVENT_REMOVE_REQUEST";
	public static final String EVENT_EXTRA = EventosDatabaseService.class.getName()+".EVENT_EXTRA";
	public static final String EVENT_CONFIRM_REQUEST = EventosDatabaseService.class.getName()+".EVENT_CONFIRM_REQUEST";
	public static final String EVENT_UNCONFIRM_REQUEST = EventosDatabaseService.class.getName()+".EVENT_UNCONFIRM_REQUEST";


	public enum State {
		PROCESSING,
		IDLE
	}
	public interface StateListener {
		/**
		 * Called whenever the state of the SessionBuilder is changed and when first registered.
		 * <br>Running from some thread (most likely not the ui thread)
		 * @param s
		 */
		public void onStateUpdated(State s);
	}

	private final ArrayList<StateListener> listeners = new ArrayList<StateListener>();
	private State currentState = State.IDLE;
	private LocalBroadcastManager instance;
	private Handler internetHandler;
	private final EventosDatabase db = EventosDatabase.getInstance();

	private void setState(final State newState) {
		synchronized (currentState) {
			currentState = newState;
		}
		synchronized (listeners) {
			for (final StateListener sl : listeners) {
				sl.onStateUpdated(newState);
			}
		}
	}
	public void registerStateListener(final StateListener l) {
		listeners.add(l);
	}
	public void unregisterStateListener(final StateListener l) {
		listeners.remove(l);
	}

	public State getState() { 
		synchronized (currentState) {
			return currentState;
		} 
	}
	@Override
	public void onCreate() {
		Log.d("onCreate");
		super.onCreate();
		instance = LocalBroadcastManager.getInstance(this);
		instance.registerReceiver(new BroadcastReceiver() {

			@Override
			public void onReceive(final Context context,
					final Intent intent) {
				final EventData event = (EventData) intent.getSerializableExtra(EVENT_EXTRA);
				remove(event);
			}

		}, new IntentFilter(EVENT_REMOVE_REQUEST));

		instance.registerReceiver(new BroadcastReceiver() {

			@Override
			public void onReceive(final Context context,
					final Intent intent) {
				final EventData event = (EventData) intent.getSerializableExtra(EVENT_EXTRA);
				confirm(event);
			}

		}, new IntentFilter(EVENT_CONFIRM_REQUEST));

		instance.registerReceiver(new BroadcastReceiver() {

			@Override
			public void onReceive(final Context context,
					final Intent intent) {
				final EventData event = (EventData) intent.getSerializableExtra(EVENT_EXTRA);
				unconfirm(event);
			}

		}, new IntentFilter(EVENT_UNCONFIRM_REQUEST));

		final HandlerThread handlerThread = new HandlerThread("EventosDatabaseService");
		final HandlerThread internetThread = new HandlerThread("EventosFileSyncer");
		handlerThread.start();
		internetThread.start();
		looper = handlerThread.getLooper();
		handler = new Handler(looper);
		internetHandler = new Handler(internetThread.getLooper());
	}







	//	//TODO: have to add the unconfirmed to the archive and "pop" last confirmed version of the event
	//	protected void unconfirm(final EventData event) {
	//		handler.post(new Runnable() {
	//
	//			@Override
	//			public void run() {
	//				setState(State.PROCESSING);
	//				db.markRequest(event);
	//				db.sync(event);
	//				broadcater.broadcast(new Delegate<EventosDatabaseService.DataListener>() {
	//
	//					@Override
	//					public void invoke(final DataListener t) {
	//						t.onAddition(event);
	//					}
	//				});
	//				instance.sendBroadcast(new Intent(Constants.EVENT_NOTIFY_EVENT_SET_CHANGED));
	//				setState(State.IDLE);
	//			}
	//		});
	//
	//	}
	//	protected void confirm(final EventData event) {
	//		handler.post(new Runnable() {
	//
	//			@Override
	//			public void run() {
	//				setState(State.PROCESSING);
	//				event.confirmed = true;
	//				db.markRequest(event);
	//				db.sync(event);
	//				broadcater.broadcast(new Delegate<EventosDatabaseService.DataListener>() {
	//
	//					@Override
	//					public void invoke(final DataListener t) {
	//						t.onAddition(event);
	//					}
	//				});
	//				instance.sendBroadcast(new Intent(Constants.EVENT_NOTIFY_EVENT_SET_CHANGED));
	//				setState(State.IDLE);
	//			}
	//		});
	//	}
	@Override
	public int onStartCommand(final Intent intent, final int flags, final int startId) {
		Log.d("onStartCommand");

		internetHandler.post(new Runnable() {
			@Override
			public void run() {
				try {
					final P2PConnection dbSyncer = new P2PConnection(Constants.EVENTOS_DB_FILE,  Constants.SERVER_IP+":12384");
					dbSyncer.start();
					//					dbSyncer = new P2PConnection(Constants.EVENTOS_LOCAL_DB_FILE,  Constants.SERVER_IP+":12385");
					//					dbSyncer.start();
					//					dbSyncer = new P2PConnection(Constants.EVENTOS_REMOTE_DB_FILE,  Constants.SERVER_IP+":12386");
					//					dbSyncer.start();
					//					dbSyncer = new P2PConnection(Constants.EVENTOS_REQUEST_DB_FILE,  Constants.SERVER_IP+":12387");
					//					dbSyncer.start();
					//					debugSyncer = new P2PEndPoint(Constants.EVENTOS_DEBUG_FILE, "192.168.2.109:12385", 5);
					//					pp.start();
				} catch (final IOException e) {
					e.printStackTrace();
				}
			}
		});



		return START_STICKY;
	}
	@Override
	public void onDestroy() {
		Log.d("onDestroy");
		super.onDestroy();
		cancelNotification();
		handler.post(new Runnable() {

			@Override
			public void run() {
				looper.quit();
				Process.killProcess(Process.myPid());
			}

		});
	}
	@Override
	public IBinder onBind(final Intent arg0) {
		return new EventosDatabaseServiceBinder();
	}
	public class EventosDatabaseServiceBinder extends Binder {
		public EventosDatabaseService getEventosService() {
			return EventosDatabaseService.this;
		}
	}

	private void cancelNotification() {
		final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		notificationManager.cancel(1);
	}


	private final Broadcaster<DataListener> broadcater = new Broadcaster<EventosDatabaseService.DataListener>();
	public boolean registerDataListener(final DataListener listener) {
		return broadcater.register(listener);
	}
	public void unregisterDataListener(final DataListener l) {
		broadcater.unregister(l);
	}
	public static interface DataListener {
		public void onUpdate(EventData older, EventData newer);
		public void onAddition(EventData added);
		public void onRemoval(EventData removed);
		public void onAddition(List<EventData> all);
	}
	public void add(final EventData event) {
		handler.post(new Runnable() {

			@Override
			public void run() {
				setState(State.PROCESSING);
				if ( event.eidOwner.equals(EventosApplication.EVENTOS_USER.id) ) {
					db.add(event);
				}
				broadcater.broadcast(new Delegate<EventosDatabaseService.DataListener>() {

					@Override
					public void invoke(final DataListener t) {
						t.onAddition(event);
					}
				});
				instance.sendBroadcast(new Intent(Constants.EVENT_NOTIFY_EVENT_SET_CHANGED));
				setState(State.IDLE);
			}
		});
	}

	public void remove(final EventData event) {
		handler.post(new Runnable() {

			@Override
			public void run() {
				setState(State.PROCESSING);
				db.remove(event);
				broadcater.broadcast(new Delegate<EventosDatabaseService.DataListener>() {

					@Override
					public void invoke(final DataListener t) {
						t.onRemoval(event);
					}
				});
				instance.sendBroadcast(new Intent(Constants.EVENT_NOTIFY_EVENT_SET_CHANGED));
				setState(State.IDLE);
			}
		});
	}

	public void searchEvents(final SearchDefiner search, final Delegate<ArrayList<EventData>> delegate) {
		handler.post(new Runnable() {
			@Override
			public void run() {
				setState(State.PROCESSING);
				final ArrayList<EventData> events = db.getEvents(new EventFilter() {

					@Override
					public boolean filter(final EventData eventInfo) {
						return eventInfo.isRelevant(search);
					}
				});
				setState(State.IDLE);
				delegate.invoke(events);
			}
		});

	}


	public ArrayList<Type> getTypes() {
		return db.getTypes();
	}




	public void update(final EventData event) {
		handler.post(new Runnable() {

			@Override
			public void run() {
				setState(State.PROCESSING);
				db.update(event);
				broadcater.broadcast(new Delegate<EventosDatabaseService.DataListener>() {
					@Override
					public void invoke(final DataListener t) {
						t.onUpdate(event,event);
					}
				});
				instance.sendBroadcast(new Intent(Constants.EVENT_NOTIFY_EVENT_SET_CHANGED));
				setState(State.IDLE);
			}
		});
	}


	public HashMap<String, Long> getLastKnowns() {
		return db.getLastKnowns();
	}


	public void addAll(final ArrayList<EventData> all) {
		handler.post(new Runnable() {

			@Override
			public void run() {
				setState(State.PROCESSING);
				db.addAll(all);
				broadcater.broadcast(new Delegate<EventosDatabaseService.DataListener>() {

					@Override
					public void invoke(final DataListener t) {
						t.onAddition(all);
					}
				});
				instance.sendBroadcast(new Intent(Constants.EVENT_NOTIFY_EVENT_SET_CHANGED));
				setState(State.IDLE);
			}
		});
	}


	public Type getType(final Long typeId) {
		return db.getType(typeId);
	}

	/**
	 * 
	 * @return highest last modified out of all the events that are owned by <b>this user</b>
	 */
	public void lockDB() {
		db.lock();
	}

	public void unlockDB() {
		db.unlock();
	}
	public ArrayList<EventData> getNewEvents(final HashMap<String, Long> currentEventsKnown) {

		return db.getNewEvents(currentEventsKnown);
	}

	//	public boolean exists(final String uid, final long eid) {
	//		return db.exists(uid, eid);
	//	}
	public void sync(final EventData e) {
		setState(State.PROCESSING);
		db.sync(e);
		broadcater.broadcast(new Delegate<EventosDatabaseService.DataListener>() {

			@Override
			public void invoke(final DataListener t) {
				t.onAddition(e);
			}
		});
		instance.sendBroadcast(new Intent(Constants.EVENT_NOTIFY_EVENT_SET_CHANGED));
		setState(State.IDLE);

	}
	public ArrayList<EventData> getAllUnconfirmed() {
		return db.getAllUnconfirmed();
	}

	/**
	 * @param longValue
	 * @return
	 */
	public Field getField(final long longValue) {
		return db.getField(longValue);
	}
	/**
	 * @return
	 */
	public Field newField(final Field f) {
		return db.newField(f);
	}
	/**
	 * @return
	 */
	public ArrayList<Request> getAllRequestsAfter(final long rid) {
		return db.getAllRequestsAfter(rid);
	}
	/**
	 * 
	 */
	public void sync() {
	}



}
